package protocol

import (
	"math/big"

	"github.com/iotexproject/go-pkgs/hash"
	"github.com/pkg/errors"

	"github.com/iotexproject/iotex-core/v2/state"
)

// NamespaceOption creates an option for given namesapce
func NamespaceOption(ns string) StateOption {
	return func(sc *StateConfig) error {
		sc.Namespace = ns
		return nil
	}
}

// KeyOption sets the key for call
func KeyOption(key []byte) StateOption {
	return func(cfg *StateConfig) error {
		cfg.Key = make([]byte, len(key))
		copy(cfg.Key, key)
		return nil
	}
}

// KeysOption sets the key for call
func KeysOption(f func() ([][]byte, error)) StateOption {
	return func(cfg *StateConfig) (err error) {
		cfg.Keys, err = f()
		return err
	}
}

// LegacyKeyOption sets the key for call with legacy key
func LegacyKeyOption(key hash.Hash160) StateOption {
	return func(cfg *StateConfig) error {
		cfg.Key = make([]byte, len(key[:]))
		copy(cfg.Key, key[:])
		return nil
	}
}

// CreateStateConfig creates a config for accessing stateDB
func CreateStateConfig(opts ...StateOption) (*StateConfig, error) {
	cfg := StateConfig{}
	for _, opt := range opts {
		if err := opt(&cfg); err != nil {
			return nil, errors.Wrap(err, "failed to execute state option")
		}
	}
	return &cfg, nil
}

// ObjectOption sets the object for call
func ObjectOption(obj any) StateOption {
	return func(cfg *StateConfig) error {
		cfg.Object = obj
		return nil
	}
}

// ErigonStoreOnlyOption sets the option to only read/write from/to erigon store
func ErigonStoreOnlyOption() StateOption {
	return func(cfg *StateConfig) error {
		cfg.ErigonStoreOnly = true
		return nil
	}
}

// ErigonStoreKeyOption sets the erigon store key for call
func ErigonStoreKeyOption(key []byte) StateOption {
	return func(cfg *StateConfig) error {
		cfg.ErigonStoreKey = make([]byte, len(key))
		copy(cfg.ErigonStoreKey, key)
		return nil
	}
}

type (
	// StateConfig is the config for accessing stateDB
	StateConfig struct {
		Namespace       string // namespace used by state's storage
		Key             []byte
		Keys            [][]byte
		Object          any    // object used by state's storage
		ErigonStoreOnly bool   // whether only read/write from/to erigon store
		ErigonStoreKey  []byte // erigon store key used by state's storage. If nil, use Key field
	}

	// StateOption sets parameter for access state
	StateOption func(*StateConfig) error

	// StateReader defines an interface to read stateDB
	StateReader interface {
		Height() (uint64, error)
		State(interface{}, ...StateOption) (uint64, error)
		States(...StateOption) (uint64, state.Iterator, error)
		ReadView(string) (View, error)
	}

	// StateManager defines the stateDB interface atop IoTeX blockchain
	StateManager interface {
		StateReader
		// Accounts
		Snapshot() int
		Revert(int) error
		// General state
		PutState(interface{}, ...StateOption) (uint64, error)
		DelState(...StateOption) (uint64, error)
		WriteView(string, View) error
	}

	StateManagerWithCloser interface {
		StateManager
		Close()
	}
)

type (
	SimulateOption       func(*SimulateOptionConfig)
	SimulateOptionConfig struct {
		PreOpt     func(StateManager) error
		Nonce, Gas uint64
		GasPrice   *big.Int
	}
)

func WithSimulatePreOpt(fn func(StateManager) error) SimulateOption {
	return func(so *SimulateOptionConfig) {
		so.PreOpt = fn
	}
}
